{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "f728f24e-a7c2-4ad3-a4d7-18a41bc5f1b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import TensorDataset, random_split\n",
    "from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts\n",
    "from scipy.io import loadmat\n",
    "import pandas as pd\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da6cdc6a-a81a-4342-a64e-7ba5b37ed4a8",
   "metadata": {},
   "source": [
    "# 读取数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e6bd3d05-6ce7-4941-90c8-a5ea71d27129",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'dict'>\n",
      "dict_keys(['__header__', '__version__', '__globals__', 'Label', 'feature'])\n",
      "torch.Size([48000, 2, 16, 32])\n",
      "tensor(1.)\n",
      "tensor(0.)\n"
     ]
    }
   ],
   "source": [
    "data = loadmat('../autodl-tmp/NetworkData.mat')\n",
    "print(type(data))\n",
    "print(data.keys())\n",
    "\n",
    "feature = torch.from_numpy(data['feature']).to(torch.complex64).unsqueeze(1)\n",
    "labels = torch.from_numpy(data['Label']).to(torch.complex64).unsqueeze(1)\n",
    "\n",
    "# 归一化操作：对每个通道进行 min-max 归一化\n",
    "def min_max_normalize(tensor):\n",
    "    # 按照 (C, H, W) 维度进行归一化（通常情况下 C 是第一个维度）\n",
    "    min_val = tensor.min(dim=2, keepdim=True).values  # 对每个像素点的通道（dim=2）进行最小值操作\n",
    "    max_val = tensor.max(dim=2, keepdim=True).values  # 对每个像素点的通道（dim=2）进行最大值操作\n",
    "    return (tensor - min_val) / (max_val - min_val)\n",
    "\n",
    "# 分别处理实部和虚部\n",
    "feature_real = min_max_normalize(feature.real)\n",
    "feature_imag = min_max_normalize(feature.imag)\n",
    "labels_real = min_max_normalize(labels.real)\n",
    "labels_imag = min_max_normalize(labels.imag)\n",
    "\n",
    "# 合并实部和虚部\n",
    "feature = torch.cat([feature_real, feature_imag], dim=1)\n",
    "labels = torch.cat([labels_real, labels_imag], dim=1)\n",
    "\n",
    "print(feature.shape)\n",
    "print(torch.max(feature))\n",
    "print(torch.min(feature))\n",
    "\n",
    "dataset = TensorDataset(feature, labels)\n",
    "len_dataset = len(dataset)\n",
    "# 将长度计算结果转换为整数类型\n",
    "train_size = int(0.8 * len_dataset)\n",
    "valid_size = len_dataset - train_size\n",
    "train_data, valid_data = random_split(dataset, [train_size, valid_size])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95f44f40-dc08-4384-a008-411d762b1217",
   "metadata": {},
   "source": [
    "# 定义网络"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f2937880-6599-400b-aa90-9d3560e6fc88",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 编码器类\n",
    "class Encoder(nn.Module):\n",
    "    def __init__(self, C_):\n",
    "        super(Encoder, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(2, 16, kernel_size=4, stride=2, padding=1)\n",
    "        self.conv2 = nn.Conv2d(16, C_, kernel_size=4, stride=2, padding=1)\n",
    "    def forward(self, X):\n",
    "        Y1 = torch.relu(self.conv1(X))\n",
    "        Y = torch.relu(self.conv2(Y1))\n",
    "        return Y\n",
    "\n",
    "# 量化与二进制转换类\n",
    "class ReshapeAndQuant(nn.Module):\n",
    "    def __init__(self, q_):\n",
    "        super(ReshapeAndQuant, self).__init__()\n",
    "        self.q = q_  # 量化位数\n",
    "    def forward(self, X, v):\n",
    "        v = X.view(X.size(0), -1) # reshape\n",
    "        Z = torch.max(v) - torch.min(v)\n",
    "        mu = Z / (2 ** self.q)\n",
    "        v_quant = mu * torch.floor(v / mu)\n",
    "        v_bit = torch.round(v_quant)\n",
    "        return v_bit\n",
    "\n",
    "# 解量化与重塑类\n",
    "class DequanAndReshape(nn.Module):\n",
    "    def __init__(self, q_):\n",
    "        super(DequanAndReshape, self).__init__()\n",
    "        self.q = q_\n",
    "    def forward(self, v_bit, C_, M_, N_, N0_, Z_):\n",
    "        mu = Z_ / (2 ** self.q)\n",
    "        v_dequant = mu * v_bit\n",
    "        batch_size_ = v_bit.size(0)\n",
    "        v_reshape = v_dequant.view((batch_size_, C_, M_ // 4, N // 4 // N0)).clone()\n",
    "        return v_reshape\n",
    "\n",
    "# 残差块类\n",
    "class Residual(nn.Module):  \n",
    "    def __init__(self, input_channels, num_channels):\n",
    "        super().__init__()\n",
    "        self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, stride=1, padding=1)\n",
    "        self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, stride=1, padding=1) \n",
    "        self.bn1 = nn.BatchNorm2d(num_channels)\n",
    "        self.bn2 = nn.BatchNorm2d(num_channels)\n",
    "    def forward(self, X):\n",
    "        Y1 = F.relu(self.bn1(self.conv1(X)))\n",
    "        Y = F.relu(self.bn2(self.conv2(Y1)))\n",
    "        Y = Y + X  # 替代Y += X，避免就地操作\n",
    "        return Y\n",
    "# 定义残差网络块\n",
    "def resnet_block(input_channels, num_channels, num_residuals):\n",
    "    blk = []\n",
    "    for i in range(num_residuals):\n",
    "        blk.append(Residual(num_channels, num_channels))\n",
    "    return blk\n",
    "\n",
    "# 解码器类\n",
    "class Decoder(nn.Module):\n",
    "    def __init__(self, C_, B_, N0_):\n",
    "        super(Decoder, self).__init__()\n",
    "        self.tconv1 = nn.ConvTranspose2d(C_, 16, kernel_size=4, stride=2, padding=1)\n",
    "        self.tconv2 = nn.ConvTranspose2d(16, 16, kernel_size=4, stride=2, padding=1)\n",
    "        self.resblocks = nn.Sequential(*resnet_block(16, 16, B_))\n",
    "        self.upsampling = nn.ConvTranspose2d(16, 16, kernel_size=3, stride=(1, N0_), padding=1, output_padding=(0, N0_-1))\n",
    "        self.final_conv = nn.Conv2d(16, 2, kernel_size=3, stride=1, padding=1)  \n",
    "    def forward(self, X):\n",
    "        # 2 x (TConv + ReLU) \n",
    "        Y1 = F.relu(self.tconv2(F.relu(self.tconv1(X))))\n",
    "        # B x ResBlock\n",
    "        Y2 = self.resblocks(Y1)\n",
    "        # Upsampling\n",
    "        Y3 = self.upsampling(Y2)\n",
    "        # final Conv\n",
    "        Y = self.final_conv(Y3)\n",
    "        return Y\n",
    "\n",
    "# 组合网络类\n",
    "class JDCNet(nn.Module):\n",
    "    def __init__(self, encoder, reshape_and_quant,\n",
    "                 dequan_and_reshape, decoder, C_, M_, N_, N0_):\n",
    "        super(JDCNet, self).__init__()\n",
    "        self.encoder = encoder\n",
    "        self.reshape_and_quant = reshape_and_quant\n",
    "        self.dequan_and_reshape = dequan_and_reshape\n",
    "        self.decoder = decoder\n",
    "        self.C = C_\n",
    "        self.M = M_\n",
    "        self.N = N_\n",
    "        self.N0 = N0_\n",
    "    def forward(self, X):\n",
    "        # 编码器部分\n",
    "        encoded_output = self.encoder(X)\n",
    "        v_bit = self.reshape_and_quant(encoded_output, X)\n",
    "\n",
    "        # 这里假设直接通过网络获得Z，实际中经过传播后应该有另外的方法是的BS得到Z\n",
    "        Z_ = torch.max(encoded_output) - torch.min(encoded_output)\n",
    "        v_reshape = self.dequan_and_reshape(v_bit, self.C, self.M, self.N, self.N0, Z_) \n",
    "\n",
    "        # 解码器部分\n",
    "        H_hat = self.decoder(v_reshape)\n",
    "\n",
    "        return H_hat"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd548025-634b-41b9-a51e-32bb3ef30e0d",
   "metadata": {},
   "source": [
    "# 定义损失函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c651ebe0-c80b-4f99-9ec5-dd95b5797ade",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.linalg as LA\n",
    "\n",
    "# 定义均方误差损失函数\n",
    "def mse_loss(predictions, targets):\n",
    "    Loss = 0\n",
    "    for i in range(len(predictions)):\n",
    "        H = torch.complex(targets[i,0], targets[i,1])\n",
    "        H_hat = torch.complex(predictions[i,0], predictions[i,1])\n",
    "        Loss += LA.norm((H_hat - H), ord=2) ** 2\n",
    "    \n",
    "    return Loss\n",
    "    \n",
    "def nmse_metric(predictions, targets):\n",
    "    mse = 0\n",
    "    for i in range(len(predictions)):\n",
    "        H = torch.complex(targets[i,0], targets[i,1])\n",
    "        H_hat = torch.complex(predictions[i,0], predictions[i,1])\n",
    "        mse += LA.norm((H_hat - H), ord=2) ** 2\n",
    "    mse = mse / len(predictions)\n",
    "    \n",
    "    var = 0\n",
    "    for i in range(len(predictions)):\n",
    "        H = torch.complex(targets[i,0], targets[i,1])\n",
    "        var += LA.norm((H), ord=2) ** 2\n",
    "    var = var / len(predictions)\n",
    "    \n",
    "    return (10 * torch.log10(mse / var)) \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45e24c4d-6cb5-4d15-8653-5b1b615e1789",
   "metadata": {},
   "source": [
    "# 训练及可视化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "90f9c7e4-ebef-4ceb-b615-889789ba1413",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.animation as animation\n",
    "import torch\n",
    "\n",
    "class Animator:\n",
    "    \"\"\"For plotting data in animation.\"\"\"\n",
    "    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,\n",
    "                 ylim=None, xscale='linear', yscale='linear',\n",
    "                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,\n",
    "                 figsize=(3.5, 2.5)):\n",
    "        if legend is None:\n",
    "            legend = []\n",
    "        self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize)\n",
    "        if nrows * ncols == 1:\n",
    "            self.axes = [self.axes, ]\n",
    "        self.config_axes = lambda: self.set_axes(\n",
    "            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)\n",
    "        self.X, self.Y, self.fmts = None, None, fmts\n",
    "        self.lines = []\n",
    "        self.legend = legend\n",
    "\n",
    "    def set_axes(self, axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):\n",
    "        axes.set_xlabel(xlabel)\n",
    "        axes.set_ylabel(ylabel)\n",
    "        axes.set_xscale(xscale)\n",
    "        axes.set_yscale(yscale)\n",
    "        if xlim:\n",
    "            axes.set_xlim(xlim)\n",
    "        if ylim:\n",
    "            axes.set_ylim(ylim)\n",
    "        if legend:\n",
    "            axes.legend(legend)\n",
    "\n",
    "    def add(self, x, *ys):\n",
    "        \"\"\"Add new data points to the plot.\"\"\"\n",
    "        n = len(ys)\n",
    "        if not self.X:\n",
    "            self.X = [[] for _ in range(n)]\n",
    "        if not self.Y:\n",
    "            self.Y = [[] for _ in range(n)]\n",
    "        for i, y in enumerate(ys):\n",
    "            if x is not None and y is not None:\n",
    "                self.X[i].append(x)\n",
    "                self.Y[i].append(y)\n",
    "        self.update_plot()\n",
    "\n",
    "    def update_plot(self):\n",
    "        \"\"\"Update the plot with new data.\"\"\"\n",
    "        for line in self.lines:\n",
    "            line.set_data([], [])\n",
    "        self.lines = []\n",
    "        for x, y, fmt in zip(self.X, self.Y, self.fmts):\n",
    "            line, = self.axes[0].plot(x, y, fmt)\n",
    "            self.lines.append(line)\n",
    "        self.config_axes()\n",
    "        self.fig.canvas.draw()  # Force the figure to update"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "20fe5eff-56ae-4283-9e7c-e02a221262da",
   "metadata": {},
   "outputs": [],
   "source": [
    "class CosineAnnealingWarmUpRestarts(torch.optim.lr_scheduler._LRScheduler):\n",
    "    def __init__(self, optimizer, T_0, T_w, eta_max, eta_min=5e-5, last_epoch=-1):\n",
    "        self.T_0 = T_0  # 最大调整周期\n",
    "        self.T_w = T_w  # 预热周期\n",
    "        self.eta_max = eta_max  # 最大学习率\n",
    "        self.eta_min = eta_min  # 最小学习率\n",
    "        super(CosineAnnealingWarmUpRestarts, self).__init__(optimizer, last_epoch)\n",
    "\n",
    "    def get_lr(self):\n",
    "        epoch = self.last_epoch\n",
    "        if epoch < self.T_w:\n",
    "            # 预热阶段：线性增加学习率\n",
    "            eta_t = self.eta_min + (self.eta_max - self.eta_min) * epoch / self.T_w\n",
    "        else:\n",
    "            # 余弦退火阶段：逐渐减小学习率\n",
    "            cos_inner = (epoch - self.T_w) / (self.T_0 - self.T_w)\n",
    "            cos_inner = torch.tensor(cos_inner, dtype=torch.float32)  # 将cos_inner转换为Tensor\n",
    "            cos_out = torch.cos(torch.pi * cos_inner) + 1\n",
    "            eta_t = self.eta_min + (self.eta_max - self.eta_min) / 2 * cos_out\n",
    "        return [eta_t for _ in self.optimizer.param_groups]\n",
    "\n",
    "\n",
    "def train(net, train_loader, valid_loader, num_epochs, learning_rate, weight_decay, batch_size, device):\n",
    "    net.to(device)\n",
    "    train_ls, valid_ls = [], []\n",
    "\n",
    "    # 先定义优化器\n",
    "    optimizer = torch.optim.Adam(net.parameters(), lr=learning_rate, weight_decay=weight_decay)\n",
    "    \n",
    "    # 然后定义调度器\n",
    "    scheduler = CosineAnnealingWarmUpRestarts(optimizer, T_0=200, T_w=30, eta_max=2e-3, eta_min=5e-5)\n",
    "    \n",
    "    # Initialize Animator\n",
    "    animator = Animator(xlabel='Epoch', ylabel='Loss', legend=['Train Loss', 'Valid Loss'])\n",
    "\n",
    "    for epoch in range(num_epochs):\n",
    "        net.train()\n",
    "        running_loss = 0.0\n",
    "        for X, y in train_loader:\n",
    "            X, y = X.to(device), y.to(device)\n",
    "            optimizer.zero_grad()\n",
    "            y_pred = net(X)\n",
    "            # print(y_pred.shape)\n",
    "            loss = mse_loss(y_pred, y)\n",
    "            loss.backward()\n",
    "            optimizer.step()\n",
    "            running_loss += loss.item()\n",
    "\n",
    "        train_ls.append(running_loss / len(train_loader))\n",
    "\n",
    "        # Validation loss\n",
    "        net.eval()\n",
    "        valid_loss  = 0.0\n",
    "        with torch.no_grad():\n",
    "            for X_val, y_val in valid_loader:\n",
    "                X_val, y_val = X_val.to(device), y_val.to(device)\n",
    "                y_pred_val = net(X_val)\n",
    "                valid_loss += mse_loss(y_pred_val, y_val).item()\n",
    "                \n",
    "        valid_ls.append(valid_loss / len(valid_loader))\n",
    "\n",
    "        # Update animator with the losses\n",
    "        animator.add(epoch + 1, train_ls[-1], valid_ls[-1])\n",
    "\n",
    "        # 更新学习率\n",
    "        scheduler.step()\n",
    "\n",
    "        # 打印每个epoch的损失和学习率\n",
    "        print(f\"Epoch [{epoch + 1}/{num_epochs}], train loss: {train_ls[-1]:.4f}, valid loss: {valid_ls[-1]:.4f}, LR: {scheduler.get_lr()[0]:.8f}\")\n",
    "\n",
    "    # 如果是最后一个 epoch，计算 NMSE 指标\n",
    "    if epoch == num_epochs - 1:\n",
    "        # 计算训练集的 NMSE\n",
    "        net.eval()\n",
    "        total_train_nmse = 0.0\n",
    "        with torch.no_grad():\n",
    "            for X_train, y_train in train_loader:\n",
    "                X_train, y_train = X_train.to(device), y_train.to(device)\n",
    "                y_pred_train = net(X_train)\n",
    "                total_train_nmse += nmse_metric(y_pred_train, y_train).item()\n",
    "\n",
    "        avg_train_nmse = total_train_nmse / len(train_loader)\n",
    "        print(f\"Final Train NMSE: {avg_train_nmse:.4f}\")\n",
    "\n",
    "        # 计算验证集的 NMSE\n",
    "        total_valid_nmse = 0.0\n",
    "        with torch.no_grad():\n",
    "            for X_val, y_val in valid_loader:\n",
    "                X_val, y_val = X_val.to(device), y_val.to(device)\n",
    "                y_pred_val = net(X_val)\n",
    "                total_valid_nmse += nmse_metric(y_pred_val, y_val).item()\n",
    "\n",
    "        avg_valid_nmse = total_valid_nmse / len(valid_loader)\n",
    "        print(f\"Final Valid NMSE: {avg_valid_nmse:.4f}\")\n",
    "\n",
    "    return train_ls, valid_ls\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "924387c6-090d-4f59-8651-e3aa224fe933",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [1/20], train loss: 36637.3556, valid loss: 18324.3407, LR: 0.00011500\n",
      "Epoch [2/20], train loss: 17997.3131, valid loss: 17669.1655, LR: 0.00018000\n",
      "Epoch [3/20], train loss: 17717.5930, valid loss: 17661.7342, LR: 0.00024500\n",
      "Epoch [4/20], train loss: 17629.6381, valid loss: 17573.6212, LR: 0.00031000\n",
      "Epoch [5/20], train loss: 17590.1086, valid loss: 17559.2618, LR: 0.00037500\n",
      "Epoch [6/20], train loss: 17572.5696, valid loss: 17722.6544, LR: 0.00044000\n",
      "Epoch [7/20], train loss: 17563.3860, valid loss: 17578.0224, LR: 0.00050500\n",
      "Epoch [8/20], train loss: 17556.4607, valid loss: 17957.7409, LR: 0.00057000\n",
      "Epoch [9/20], train loss: 17553.2199, valid loss: 17600.3211, LR: 0.00063500\n",
      "Epoch [10/20], train loss: 17552.7688, valid loss: 17455.5726, LR: 0.00070000\n",
      "Epoch [11/20], train loss: 17551.4684, valid loss: 18235.2481, LR: 0.00076500\n",
      "Epoch [12/20], train loss: 17550.4671, valid loss: 18269.4091, LR: 0.00083000\n",
      "Epoch [13/20], train loss: 17551.2206, valid loss: 17730.8701, LR: 0.00089500\n",
      "Epoch [14/20], train loss: 17549.4603, valid loss: 17505.0067, LR: 0.00096000\n",
      "Epoch [15/20], train loss: 17549.7315, valid loss: 17496.1229, LR: 0.00102500\n",
      "Epoch [16/20], train loss: 17549.8715, valid loss: 17484.9429, LR: 0.00109000\n",
      "Epoch [17/20], train loss: 17548.0490, valid loss: 17456.4538, LR: 0.00115500\n",
      "Epoch [18/20], train loss: 17547.8065, valid loss: 17459.3035, LR: 0.00122000\n",
      "Epoch [19/20], train loss: 17547.0898, valid loss: 17531.4120, LR: 0.00128500\n",
      "Epoch [20/20], train loss: 17546.2458, valid loss: 17417.0670, LR: 0.00135000\n",
      "Final Train NMSE: -8.8705\n",
      "Final Valid NMSE: -8.8466\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 350x250 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Hyperparameters\n",
    "C = 8\n",
    "M = 16\n",
    "N = 64\n",
    "N0 = 2  # 2 or 4\n",
    "B = 4\n",
    "q = 8\n",
    "batch_size = 256\n",
    "num_workers = 16\n",
    "epochs = 20\n",
    "lr = 0.002\n",
    "weight_decay = 0\n",
    "# 超参数设置\n",
    "eta_max = 2e-3\n",
    "eta_min = 5e-5\n",
    "T_w = 30  # 预热周期\n",
    "T_0 = 200  # 最大调整周期S\n",
    "\n",
    "\n",
    "train_loader = torch.utils.data.DataLoader(train_data, \n",
    "                batch_size=batch_size, shuffle=True, \n",
    "                num_workers=num_workers, pin_memory=True)\n",
    "valid_loader = torch.utils.data.DataLoader(valid_data, \n",
    "                batch_size=batch_size, shuffle=False, \n",
    "                num_workers=num_workers, pin_memory=True)\n",
    "\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "\n",
    "# Instantiate the network\n",
    "encoder = Encoder(C_=C)\n",
    "reshape_and_quant = ReshapeAndQuant(q_=q)\n",
    "dequan_and_reshape = DequanAndReshape(q_=q)\n",
    "decoder = Decoder(C_=C, B_=B, N0_=N0)\n",
    "net = JDCNet(encoder, reshape_and_quant, dequan_and_reshape, decoder,\n",
    "            C_=C, M_=M, N_=N, N0_=N0)\n",
    "\n",
    "# 在模型实例化后使用 DataParallel，实现数据并行\n",
    "net = torch.nn.DataParallel(net)\n",
    "\n",
    "net.to(device)\n",
    "\n",
    "train_net = train(net, train_loader, valid_loader, num_epochs=epochs, learning_rate=eta_max, \n",
    "      weight_decay=0, batch_size=256, device=device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "09c9346f-c032-43b6-bd6f-4397291494a4",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
